#include <string>

#if _WIN32
    #include <Windows.h>
inline std::wstring fromUtf8(const std::string &input)
{
    const int len =
        MultiByteToWideChar(CP_ACP, 0, input.c_str(), -1, nullptr, 0);
    std::wstring out_wide_string;
    out_wide_string.resize(len + 1);
    out_wide_string[len] = 0;
    MultiByteToWideChar(CP_ACP, 0, input.c_str(), -1, &out_wide_string[0], len);
    out_wide_string.resize(len - 1);
    return out_wide_string;
}
#endif

#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <iomanip>
#include <algorithm>

using namespace std;

static vector<string> get_spirv_files(string list_file)
{
    vector<string> files;
#if _WIN32
    wstring wpath = fromUtf8(list_file);
    fstream ifs(wpath, ios::in);
#else
    fstream       ifs(list_file, ios::in);
#endif

    if (ifs.is_open()) {
        char linebuffer[512] {0};
        while (ifs.getline(linebuffer, sizeof(linebuffer) - 1, '\n')) {
            // cout << "Found File: " << linebuffer << "\n";
            files.emplace_back(linebuffer);
        }
    }

    return files;
}

static int write_content(ofstream &ofs, string spirv_file)
{
#if _WIN32
    wstring wpath = fromUtf8(spirv_file);
    fstream ifs(wpath, ios::in | ios::binary);
#else
    fstream       ifs(spirv_file, ios::in | ios::binary);
#endif
    if (ifs.is_open()) {
        // get var name
        auto pos = spirv_file.find_last_of("/\\");
        if (pos == string::npos) {
            return 2;
        }
        auto filename = spirv_file.substr(pos + 1);
        replace(begin(filename), end(filename), '.', '_');

        // length
        ifs.seekg(0, ios::end);
        ofs << "    const u32 " << filename << "_length = " << ifs.tellg()
            << ";\n";
        ifs.seekg(0);

        // data
        ofs << "    const u8  " << filename << "_data[] {";
        unsigned char ch;
        int           cnt = 0;
        while (ifs.read((char *)&ch, 1)) {
            if (cnt++ % 16 == 0)
                ofs << "\n        ";
            ofs << setw(3) << setfill(' ') << (int)ch << ", ";
        }

        ofs << "\n    };\n";
    } else {
        return 1;
    }
    return 0;
}

static int write_cpp_file(const vector<string> &spirv_files,
                          const char           *cpp_file)
{
#if _WIN32
    std::ofstream ofs(fromUtf8(cpp_file), ios::out | ios::trunc);
#else
    std::ofstream ofs(cpp_file, ios::out | ios::trunc);
#endif
    if (ofs.is_open()) {
        // header
        ofs << "#include \"shaders.hpp\"\n";
        ofs << "namespace core::shaders {\n";

        // spirv contents
        for (const auto &file : spirv_files) {
            int r = write_content(ofs, file);
            if (r != 0) {
                cout << "processing file " << file << " return code " << r
                     << '\n';
                return 4;
            }
        }

        // footer
        ofs << "}\n";
    } else {
        cout << "could not open file to write: " << cpp_file << '\n';
        return 3;
    }
    return 0;
}

int main(int argc, char *argv[])
{
    if (argc != 3) {
        cout << "your show call this tool via:\n";
        cout << "  " << argv[0] << " <shader_spirv_list> <output_cpp>\n";
        return 1;
    }

    auto files = get_spirv_files(argv[1]);
    if (files.empty()) {
        cout << "the shader file list have no spirv files!\n";
        return 2;
    }
    return write_cpp_file(files, argv[2]);
}
